package browser

import (
	"context"
	"encoding/json"
	"errors"
	"sync"
	"time"

	"golang.org/x/net/websocket"

	"gitee.com/baixudong/gospider/requests"
	"go.uber.org/atomic"
)

type commend struct {
	Id     int64          `json:"id"`
	Method string         `json:"method"`
	Params map[string]any `json:"params"`
}
type commendData struct {
	ctx      context.Context
	cnl      context.CancelFunc
	recvData recvData
}

// [error:map[code:-32601 message:'DOM2.querySelector' wasn't found]

type RecvError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}
type recvData struct {
	Id     int64          `json:"id"`
	Method string         `json:"method"`
	Params map[string]any `json:"params"`
	Result map[string]any `json:"result"`
	Error  RecvError      `json:"error"`
}

type webSock struct {
	commends  map[int64]*commendData
	events    map[string]*commendData
	conn      *websocket.Conn
	sendPip   chan *commendData
	ctx       context.Context
	cnl       context.CancelFunc
	id        atomic.Int64
	routeFunc func(context.Context, *Route)
	reqCli    *requests.Client
	sync.Mutex
	db *dbClient
}

type DataEntrie struct {
	Bytes string `json:"bytes"`
}

func (obj *webSock) recv(data recvData) {
	obj.Lock()
	defer obj.Unlock()
	cmdData, ok := obj.commends[data.Id]
	// log.Print(data, " >>recvData")
	if ok {
		defer cmdData.cnl()
		cmdData.recvData = data
		delete(obj.commends, data.Id)
	}
	cmdData, ok = obj.events[data.Method]
	if ok {
		defer cmdData.cnl()
		cmdData.recvData = data
		delete(obj.events, data.Method)
	}
}
func (obj *webSock) recvMain() {
	for {
		rd := recvData{}
		err := websocket.JSON.Receive(obj.conn, &rd)
		// rd2 := map[string]any{}
		// err := websocket.JSON.Receive(obj.conn, &rd2)
		// log.Print("debugger >>> ", rd2, " >>> ", err)
		// err = json.Unmarshal(tools.StringToBytes(tools.Any2json(rd2).Raw), &rd)

		if err != nil {
			obj.Close()
			return
		}
		if rd.Method == "Fetch.requestPaused" {
			routeData := RouteData{}
			temData, err := json.Marshal(rd.Params)
			if err == nil && json.Unmarshal(temData, &routeData) == nil {
				route := &Route{
					webSock:  obj,
					recvData: routeData,
					reqCli:   obj.reqCli,
				}
				if obj.routeFunc != nil {
					go obj.routeFunc(obj.ctx, route)
				} else {
					go route.Continue(obj.ctx)
				}
			}
		} else {
			obj.recv(rd)
		}
	}
}
func newWebSock(preCtx context.Context, ws, url string) (*webSock, error) {
	wsCon, err := websocket.Dial(ws, "", url)
	if err != nil {
		return nil, err
	}
	ctx, cnl := context.WithCancel(preCtx)
	reqCli := requests.NewClient(ctx)
	reqCli.DisDecode = true
	reqCli.DisUnZip = true
	cli := &webSock{
		ctx:      ctx,
		cnl:      cnl,
		sendPip:  make(chan *commendData),
		commends: map[int64]*commendData{},
		events:   map[string]*commendData{},
		conn:     wsCon,
		reqCli:   reqCli,
	}
	go cli.recvMain()
	return cli, err
}
func (obj *webSock) Close() error {
	obj.cnl()
	return obj.conn.Close()
}

func (obj *webSock) newCommendData(preCtx context.Context, cmd commend) *commendData {
	data := new(commendData)
	data.recvData.Id = cmd.Id
	data.ctx, data.cnl = context.WithCancel(preCtx)
	obj.Lock()
	defer obj.Unlock()
	obj.commends[cmd.Id] = data
	return data
}

func (obj *webSock) newEventData(preCtx context.Context, method string) *commendData {
	data := new(commendData)
	data.recvData.Method = method
	data.ctx, data.cnl = context.WithCancel(preCtx)
	obj.Lock()
	defer obj.Unlock()
	obj.events[method] = data
	return data
}

func (obj *webSock) send(preCtx context.Context, cmd commend, methods ...string) (recvData, error) {
	var ctx context.Context
	var cnl context.CancelFunc
	if preCtx == nil {
		ctx, cnl = context.WithTimeout(obj.ctx, time.Second*30)
		defer cnl()
	} else {
		ctx = preCtx
	}
	select {
	case <-obj.ctx.Done():
		return recvData{}, obj.ctx.Err()
	case <-ctx.Done():
		return recvData{}, obj.ctx.Err()
	default:
		cmd.Id = obj.id.Add(1)
		cmdData := obj.newCommendData(ctx, cmd)
		events := []*commendData{}
		for _, method := range methods {
			events = append(events, obj.newEventData(ctx, method))
		}
		err := websocket.JSON.Send(obj.conn, cmd)
		if err != nil {
			return recvData{}, err
		}
		select {
		case <-obj.ctx.Done():
			return recvData{}, obj.ctx.Err()
		case <-ctx.Done():
			return recvData{}, ctx.Err()
		case <-cmdData.ctx.Done():
			for _, event := range events {
				select {
				case <-obj.ctx.Done():
					return recvData{}, ctx.Err()
				case <-ctx.Done():
					return recvData{}, ctx.Err()
				case <-event.ctx.Done():
					if event.recvData.Error.Message != "" {
						return event.recvData, errors.New(event.recvData.Error.Message)
					}
				}
			}
			if cmdData.recvData.Error.Message != "" {
				return cmdData.recvData, errors.New(cmdData.recvData.Error.Message)
			}
			return cmdData.recvData, nil
		}
	}
}
